# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa.  If not, see <http://www.gnu.org/licenses/>.
# =============================================================================

import re
import traceback

import roman
# noinspection PyPackageRequirements
import wx
import wx.dataview
import wx.lib.agw.hyperlink
# noinspection PyPackageRequirements
import wx.lib.newevent
from logbook import Logger
# noinspection PyPackageRequirements
from wx.dataview import TreeListCtrl
from wx.lib.agw.floatspin import FloatSpin

import config
import gui.globalEvents as GE
from gui.auxFrame import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor, TextEntryValidatedDialog
from gui.builtinViews.implantEditor import BaseImplantEditorView
from gui.contextMenu import ContextMenu
from gui.utils.clipboard import fromClipboard, toClipboard
from service.character import Character
from service.esi import Esi
from service.fit import Fit
from service.market import Market


pyfalog = Logger(__name__)


def arabicOrRomanToInt(s):
    m = re.match(r'\d+$', s)
    if m:
        i = int(s)
    else:
        i = roman.fromRoman(s)
    return i


class CharacterTextValidor(BaseValidator):
    def __init__(self):
        BaseValidator.__init__(self)

    def Clone(self):
        return CharacterTextValidor()

    def Validate(self, win):
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue().strip()
        sChar = Character.getInstance()

        try:
            if len(text) == 0:
                raise ValueError("You must supply a name for the Character!")
            elif text in [x.name for x in sChar.getCharacterList()]:
                raise ValueError("Character name already in use, please choose another.")

            return True
        except ValueError as e:
            pyfalog.error(e)
            wx.MessageBox("{}".format(e), "Error")
            textCtrl.SetFocus()
            return False


class PlaceholderTextCtrl(wx.TextCtrl):
    def __init__(self, *args, **kwargs):
        self.default_text = kwargs.pop("placeholder", "")
        kwargs["value"] = self.default_text
        wx.TextCtrl.__init__(self, *args, **kwargs)
        self.Bind(wx.EVT_SET_FOCUS, self.OnFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

    def OnFocus(self, evt):
        if self.GetValue() == self.default_text:
            self.SetValue("")
        evt.Skip()

    def OnKillFocus(self, evt):
        if self.GetValue().strip() == "":
            self.SetValue(self.default_text)
        evt.Skip()

    def Reset(self):
        self.SetValue(self.default_text)


class CharacterEntityEditor(EntityEditor):
    def __init__(self, parent):
        EntityEditor.__init__(self, parent, "人物角色")
        self.SetEditorValidator(CharacterTextValidor)

    def getEntitiesFromContext(self):
        sChar = Character.getInstance()
        charList = sorted(sChar.getCharacterList(), key=lambda c: (not c.ro, c.name))

        # Do some processing to ensure that we have All 0 and All 5 at the top
        all5 = sChar.all5()
        all0 = sChar.all0()

        charList.remove(all5)
        charList.remove(all0)

        charList.insert(0, all5)
        charList.insert(0, all0)

        return charList

    def DoNew(self, name):
        sChar = Character.getInstance()
        return sChar.new(name)

    def DoRename(self, entity, name):
        sChar = Character.getInstance()

        if entity.alphaCloneID:
            trimmed_name = re.sub('[ \(\u03B1\)]+$', '', name)
            sChar.rename(entity, trimmed_name)
        else:
            sChar.rename(entity, name)

    def DoCopy(self, entity, name):
        sChar = Character.getInstance()
        copy = sChar.copy(entity)
        sChar.rename(copy, name)
        return copy

    def DoDelete(self, entity):
        sChar = Character.getInstance()
        sChar.delete(entity)


class CharacterEditor(AuxiliaryFrame):

    def __init__(self, parent):
        super().__init__(
            parent, id=wx.ID_ANY, title="角色编辑", resizeable=True, pos=wx.DefaultPosition,
            size=wx.Size(950, 650) if "wxGTK" in wx.PlatformInfo else wx.Size(850, 600))

        i = wx.Icon(BitmapLoader.getBitmap("character_small", "gui"))
        self.SetIcon(i)

        self.mainFrame = parent
        # self.disableWin = wx.WindowDisabler(self)
        sFit = Fit.getInstance()

        mainSizer = wx.BoxSizer(wx.VERTICAL)

        self.entityEditor = CharacterEntityEditor(self)
        mainSizer.Add(self.entityEditor, 0, wx.ALL | wx.EXPAND, 2)
        # Default drop down to current fit's character
        self.entityEditor.setActiveEntity(sFit.character)

        self.viewsNBContainer = wx.Notebook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)

        self.sview = SkillTreeView(self.viewsNBContainer)
        self.iview = ImplantEditorView(self.viewsNBContainer, self)
        self.aview = APIView(self.viewsNBContainer)

        self.viewsNBContainer.AddPage(self.sview, "技能")
        self.viewsNBContainer.AddPage(self.iview, "植入体")
        self.viewsNBContainer.AddPage(self.aview, "EVE SSO")

        mainSizer.Add(self.viewsNBContainer, 1, wx.EXPAND | wx.ALL, 5)

        bSizerButtons = wx.BoxSizer(wx.HORIZONTAL)

        self.btnSaveChar = wx.Button(self, wx.ID_ANY, "保存")
        self.btnSaveAs = wx.Button(self, wx.ID_ANY, "另存为")
        self.btnRevert = wx.Button(self, wx.ID_ANY, "恢复")
        self.btnOK = wx.Button(self, wx.ID_OK)

        bSizerButtons.Add(self.btnSaveChar, 0, wx.ALL, 5)
        bSizerButtons.Add(self.btnSaveAs, 0, wx.ALL, 5)
        bSizerButtons.Add(self.btnRevert, 0, wx.ALL, 5)
        bSizerButtons.AddStretchSpacer()
        bSizerButtons.Add(self.btnOK, 0, wx.ALL, 5)

        self.btnSaveChar.Bind(wx.EVT_BUTTON, self.saveChar)
        self.btnSaveAs.Bind(wx.EVT_BUTTON, self.saveCharAs)
        self.btnRevert.Bind(wx.EVT_BUTTON, self.revertChar)
        self.btnOK.Bind(wx.EVT_BUTTON, self.editingFinished)

        mainSizer.Add(bSizerButtons, 0, wx.EXPAND, 5)

        self.btnRestrict()

        self.SetSizer(mainSizer)
        self.Layout()

        self.SetMinSize(self.GetSize())
        self.Centre(wx.BOTH)

        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
        self.Bind(GE.CHAR_LIST_UPDATED, self.refreshCharacterList)
        self.entityEditor.Bind(wx.EVT_CHOICE, self.charChanged)

        self.charChanged(None)

    def btnRestrict(self):
        char = self.entityEditor.getActiveEntity()

        # enable/disable character saving stuff
        self.btnSaveChar.Enable(not char.ro and char.isDirty)
        self.btnSaveAs.Enable(char.isDirty)
        self.btnRevert.Enable(char.isDirty)
        self.sview.importBtn.Enable(not char.ro)

    def refreshCharacterList(self, event=None):
        """This is only called when we save a modified character"""
        active = self.entityEditor.getActiveEntity()
        self.entityEditor.refreshEntityList(active)
        self.btnRestrict()

        if event:
            event.Skip()

    def editingFinished(self, event):
        self.Close()

    def saveChar(self, event):
        sChr = Character.getInstance()
        char = self.entityEditor.getActiveEntity()
        sChr.saveCharacter(char.ID)
        wx.PostEvent(self, GE.CharListUpdated())

    def saveCharAs(self, event):
        char = self.entityEditor.getActiveEntity()
        self.SaveCharacterAs(self, char.ID)
        wx.PostEvent(self, GE.CharListUpdated())

    def revertChar(self, event):
        sChr = Character.getInstance()
        char = self.entityEditor.getActiveEntity()
        sChr.revertCharacter(char.ID)
        wx.PostEvent(self, GE.CharListUpdated())

    def kbEvent(self, event):
        if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
            self.Close()
            return
        event.Skip()

    def OnClose(self, event):
        wx.PostEvent(self.mainFrame, GE.CharListUpdated())
        sFit = Fit.getInstance()
        fitID = self.mainFrame.getActiveFit()
        if fitID is not None:
            sFit.clearFit(fitID)
            wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
        event.Skip()

    def restrict(self):
        self.entityEditor.btnRename.Enable(False)
        self.entityEditor.btnDelete.Enable(False)

    def unrestrict(self):
        self.entityEditor.btnRename.Enable()
        self.entityEditor.btnDelete.Enable()

    def charChanged(self, event):
        char = self.entityEditor.getActiveEntity()
        if char.name in ("All 0", "All 5"):
            self.restrict()
        else:
            self.unrestrict()

        self.btnRestrict()

        if event is not None:
            event.Skip()

    @staticmethod
    def SaveCharacterAs(parent, charID):
        sChar = Character.getInstance()
        name = sChar.getCharName(charID)

        with TextEntryValidatedDialog(
            parent, CharacterTextValidor,
            "为你的新角色起一个名字:",
            "角色另存为"
        ) as dlg:
            dlg.SetValue("{} Copy".format(name))
            dlg.txtctrl.SetInsertionPointEnd()
            dlg.CenterOnParent()

            if dlg.ShowModal() == wx.ID_OK:
                sChar = Character.getInstance()
                return sChar.saveCharacterAs(charID, dlg.txtctrl.GetValue().strip())


class SkillTreeView(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
                          style=wx.TAB_TRAVERSAL)
        self.charEditor = self.Parent.Parent  # first parent is Notebook, second is Character Editor
        self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))

        pmainSizer = wx.BoxSizer(wx.VERTICAL)

        hSizer = wx.BoxSizer(wx.HORIZONTAL)

        self.clonesChoice = wx.Choice(self, wx.ID_ANY, style=0)
        i = self.clonesChoice.Append("Omega Clone", None)
        self.clonesChoice.SetSelection(i)
        hSizer.Add(self.clonesChoice, 5, wx.ALL | wx.EXPAND, 5)

        self.searchInput = PlaceholderTextCtrl(self, wx.ID_ANY, placeholder="搜索...")
        hSizer.Add(self.searchInput, 1, wx.ALL | wx.EXPAND, 5)
        self.searchInput.Bind(wx.EVT_TEXT, self.delaySearch)

        sChar = Character.getInstance()
        self.alphaClones = sChar.getAlphaCloneList()
        char = self.charEditor.entityEditor.getActiveEntity()

        for clone in self.alphaClones:
            i = self.clonesChoice.Append(clone.alphaCloneName, clone.ID)
            if clone.ID == char.alphaCloneID:
                self.clonesChoice.SetSelection(i)

        self.clonesChoice.Bind(wx.EVT_CHOICE, self.cloneChanged)

        self.clonesChoice.SetToolTip(
            wx.ToolTip("设置一个阿尔法克隆不能代替这个角色的技能，但是可以获取到获取他们到阿尔法级别"))

        pmainSizer.Add(hSizer, 0, wx.EXPAND | wx.ALL, 5)

        # Set up timer for skill search
        self.searchTimer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.populateSkillTreeSkillSearch, self.searchTimer)

        tree = self.skillTreeListCtrl = TreeListCtrl(self, wx.ID_ANY, style=wx.dataview.TL_DEFAULT_STYLE)
        pmainSizer.Add(tree, 1, wx.EXPAND | wx.ALL, 5)

        self.imageList = wx.ImageList(16, 16)
        tree.SetImageList(self.imageList)
        self.skillBookImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small", "gui")))
        self.skillBookDirtyImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small_red", "gui")))

        tree.AppendColumn("技能")
        tree.AppendColumn("级别", align=wx.ALIGN_CENTER)
        # tree.SetMainColumn(0)

        self.root = tree.GetRootItem()
        # self.root = tree.AppendItem(root, "技能")
        #
        # tree.SetItemText(self.root, 1, "Levels")

        # first one doesn't work right in Windows. Second one doesn't work right in GTK. Together, we make sure it works.
        # Gotta love wx
        tree.SetColumnWidth(0, 525)
        tree.SetColumnWidth(1, 100)

        self.btnSecStatus = wx.Button(self, wx.ID_ANY, "安全状态: {0:.2f}".format(char.secStatus or 0.0))
        self.btnSecStatus.Bind(wx.EVT_BUTTON, self.onSecStatus)

        self.populateSkillTree()

        tree.Bind(wx.dataview.EVT_TREELIST_ITEM_ACTIVATED, self.expand)
        tree.Bind(wx.dataview.EVT_TREELIST_ITEM_EXPANDING, self.expandLookup)
        tree.Bind(wx.dataview.EVT_TREELIST_ITEM_CONTEXT_MENU, self.spawnMenu)

        bSizerButtons = wx.BoxSizer(wx.HORIZONTAL)

        bSizerButtons.Add(self.btnSecStatus, 0, wx.ALL, 5)

        bSizerButtons.AddStretchSpacer()

        importExport = (("Import", wx.ART_FILE_OPEN, "from"),
                        ("Export", wx.ART_FILE_SAVE_AS, "to"))

        for name, art, direction in importExport:
            bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON)
            btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)

            btn.SetMinSize(btn.GetSize())
            btn.SetMaxSize(btn.GetSize())

            btn.Layout()
            setattr(self, "{}Btn".format(name.lower()), btn)
            btn.Enable(True)
            nametype={"Export":"导出","Import":"导入"}
            directiontype={"from":"从","to":"到"}
            btn.SetToolTip("%s 技能 %s 剪贴板" % (nametype[name], directiontype[direction]))
            bSizerButtons.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT | wx.ALL, 5)
            btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Skills".format(name.lower())))

        pmainSizer.Add(bSizerButtons, 0, wx.EXPAND, 5)

        # bind the Character selection event
        self.charEditor.entityEditor.Bind(wx.EVT_CHOICE, self.charChanged)
        self.charEditor.Bind(GE.CHAR_LIST_UPDATED, self.populateSkillTree)

        # Context menu stuff
        self.idUnlearned = wx.NewId()
        self.levelIds = {}
        self.idLevels = {}
        self.levelIds[self.idUnlearned] = "未学"
        for level in range(6):
            id = wx.NewId()
            self.levelIds[id] = level
            self.idLevels[level] = id
        self.revertID = wx.NewId()
        self.saveID = wx.NewId()

        self.SetSizer(pmainSizer)

        # This cuases issues with GTK, see #1866
        # self.Layout()

        # For level keyboard shortcuts
        self.ChangeLevelEvent, CHANGE_LEVEL_EVENT = wx.lib.newevent.NewEvent()
        self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
        self.Bind(CHANGE_LEVEL_EVENT, self.changeLevel)

    def kbEvent(self, event):
        keyLevelMap = {
            # Regular number keys
            48: 0, 49: 1, 50: 2, 51: 3, 52: 4, 53: 5,
            # Numpad keys
            wx.WXK_NUMPAD0: 0, wx.WXK_NUMPAD1: 1, wx.WXK_NUMPAD2: 2,
            wx.WXK_NUMPAD3: 3, wx.WXK_NUMPAD4: 4, wx.WXK_NUMPAD5: 5}
        keycode = event.GetKeyCode()
        if keycode in keyLevelMap and event.GetModifiers() == wx.MOD_NONE:
            level = keyLevelMap[keycode]
            selection = self.skillTreeListCtrl.GetSelection()
            if selection:
                dataType, skillID = self.skillTreeListCtrl.GetItemData(selection)
                if dataType == 'skill':
                    event = self.ChangeLevelEvent()
                    event.SetId(self.idLevels[level])
                    wx.PostEvent(self, event)
                    return
        event.Skip()

    def importSkills(self, evt):

        with wx.MessageDialog(
            self, ("导入这个角色的技能级别被暂时缓存. 为了永久的保存技能 "
                   " 导入后请单击窗口底部的保存按钮"),
            "导入技能", wx.OK
        ) as dlg:
            dlg.ShowModal()

        text = fromClipboard().strip()
        if text:
            char = self.charEditor.entityEditor.getActiveEntity()
            try:
                lines = text.splitlines()

                for l in lines:
                    s = l.strip()
                    skill, level = s.rsplit(None, 1)[0], arabicOrRomanToInt(s.rsplit(None, 1)[1])
                    skill = char.getSkill(skill)
                    if skill:
                        skill.setLevel(level, ignoreRestrict=True)

            except (KeyboardInterrupt, SystemExit):
                raise
            except Exception as e:
                pyfalog.error(e)
                with wx.MessageDialog(self, "There was an error importing skills, please see log file", "Error", wx.ICON_ERROR) as dlg:
                    dlg.ShowModal()

            finally:
                self.charEditor.btnRestrict()
                self.populateSkillTree()
                self.charEditor.entityEditor.refreshEntityList(char)

    def exportSkills(self, evt):
        char = self.charEditor.entityEditor.getActiveEntity()

        skills = sorted(char.__class__.getSkillNameMap().keys())
        list = ""
        for s in skills:
            skill = char.getSkill(s)
            list += "{} {}\n".format(skill.item.name, skill.level)

        toClipboard(list)

    def onSecStatus(self, event):
        sChar = Character.getInstance()
        char = self.charEditor.entityEditor.getActiveEntity()
        with SecStatusDialog(self, char.secStatus or 0.0) as dlg:
            if dlg.ShowModal() == wx.ID_OK:
                value = dlg.floatSpin.GetValue()
                sChar.setSecStatus(char, value)
                self.btnSecStatus.SetLabel("安全状态: {0:.2f}".format(value))

    def delaySearch(self, evt):
        if self.searchInput.GetValue() == "" or self.searchInput.GetValue() == self.searchInput.default_text:
            self.populateSkillTree()
        else:
            self.searchTimer.Stop()
            self.searchTimer.Start(150, True)  # 150ms

    def cloneChanged(self, event):
        sChar = Character.getInstance()
        sChar.setAlphaClone(self.charEditor.entityEditor.getActiveEntity(), event.ClientData)
        self.populateSkillTree()

    def charChanged(self, event=None):
        self.searchInput.Reset()
        char = self.charEditor.entityEditor.getActiveEntity()
        for i in range(self.clonesChoice.GetCount()):
            cloneID = self.clonesChoice.GetClientData(i)
            if char.alphaCloneID == cloneID:
                self.clonesChoice.SetSelection(i)

        self.btnSecStatus.SetLabel("安全状态: {0:.2f}".format(char.secStatus or 0.0))

        self.populateSkillTree(event)

    def populateSkillTreeSkillSearch(self, event=None):
        sChar = Character.getInstance()
        char = self.charEditor.entityEditor.getActiveEntity()
        search = self.searchInput.GetLineText(0)

        root = self.root
        tree = self.skillTreeListCtrl
        tree.DeleteAllItems()

        for id, name in sChar.getSkillsByName(search):
            iconId = self.skillBookImageId
            level, dirty = sChar.getSkillLevel(char.ID, id)

            if dirty:
                iconId = self.skillBookDirtyImageId

            childId = tree.AppendItem(root, name, iconId, data=('skill', id))
            tree.SetItemText(childId, 1, "Level %d" % int(level) if isinstance(level, float) else level)

    def populateSkillTree(self, event=None):
        sChar = Character.getInstance()
        char = self.charEditor.entityEditor.getActiveEntity()
        dirtyGroups = set([skill.item.group.ID for skill in char.dirtySkills])

        if char.name in ("All 0", "All 5"):
            self.clonesChoice.Disable()
            self.btnSecStatus.Disable()
        else:
            self.clonesChoice.Enable()
            self.btnSecStatus.Enable()

        groups = sChar.getSkillGroups()
        root = self.root
        tree = self.skillTreeListCtrl
        tree.DeleteAllItems()

        for id, name in groups:
            imageId = self.skillBookImageId
            if id in dirtyGroups:
                imageId = self.skillBookDirtyImageId

            childId = tree.AppendItem(root, name, imageId, data=('group', id))
            tree.AppendItem(childId, "dummy")

        if event:
            event.Skip()

    def expand(self, event):
        root = event.GetItem()
        tree = self.skillTreeListCtrl
        if tree.IsExpanded(root):
            tree.Collapse(root)
        else:
            tree.Expand(root)

    def expandLookup(self, event):
        root = event.GetItem()
        tree = self.skillTreeListCtrl

        child = tree.GetFirstChild(root)
        if tree.GetItemText(child) == "dummy":
            tree.DeleteItem(child)

            # Get the real intrestin' stuff
            sChar = Character.getInstance()
            char = self.charEditor.entityEditor.getActiveEntity()
            data = tree.GetItemData(root)
            for id, name in sChar.getSkills(data[1]):
                iconId = self.skillBookImageId
                level, dirty = sChar.getSkillLevel(char.ID, id)

                if dirty:
                    iconId = self.skillBookDirtyImageId

                childId = tree.AppendItem(root, name, iconId, data=('skill', id))

                tree.SetItemText(childId, 1, "Level %d" % int(level) if isinstance(level, float) else level)

    def spawnMenu(self, event):
        item = event.GetItem()
        self.skillTreeListCtrl.Select(item)
        thing = self.skillTreeListCtrl.GetFirstChild(item).IsOk()
        if thing:
            return

        id = self.skillTreeListCtrl.GetItemData(item)[1]
        eveItem = Market.getInstance().getItem(id)

        srcContext = "skillItem"
        itemContext = "技能"
        context = (srcContext, itemContext)
        menu = ContextMenu.getMenu(self, eveItem, [eveItem], context)
        char = self.charEditor.entityEditor.getActiveEntity()
        if char.name not in ("All 0", "All 5"):
            menu.AppendSeparator()
            menu.Append(self.idUnlearned, "未学")
            for level in range(6):
                menu.Append(self.idLevels[level], "Level %d" % level)
            menu.Bind(wx.EVT_MENU, self.changeLevel)

        self.PopupMenu(menu)

    def changeLevel(self, event):
        level = self.levelIds.get(event.Id)

        sChar = Character.getInstance()
        char = self.charEditor.entityEditor.getActiveEntity()
        if char.name in ("All 0", "All 5"):
            return
        selection = self.skillTreeListCtrl.GetSelection()
        dataType, skillID = self.skillTreeListCtrl.GetItemData(selection)

        if level is not None:
            sChar.changeLevel(char.ID, skillID, level, persist=True)
        elif event.Id == self.revertID:
            sChar.revertLevel(char.ID, skillID)
        elif event.Id == self.saveID:
            sChar.saveSkill(char.ID, skillID)

        # After saving the skill, we need to update not just the selected skill, but all open skills due to strict skill
        # level setting. We don't want to refresh tree, as that will lose all expanded categories and users location
        # within the tree. Thus, we loop through the tree and refresh the info.
        # @todo: when collapsing branch, remove the data. This will make this loop more performant

        child = self.skillTreeListCtrl.GetFirstChild(self.root)

        def _setTreeSkillLevel(treeItem, skillID):
            lvl, dirty = sChar.getSkillLevel(char.ID, skillID)
            self.skillTreeListCtrl.SetItemText(treeItem,
                                               1,
                                               "Level {}".format(int(lvl)) if not isinstance(lvl, str) else lvl)

            if not dirty:
                self.skillTreeListCtrl.SetItemImage(treeItem, self.skillBookImageId)

        while child.IsOk():
            # child = Skill category
            dataType, id = self.skillTreeListCtrl.GetItemData(child)

            if dataType == 'skill':
                _setTreeSkillLevel(child, id)
            else:
                grand = self.skillTreeListCtrl.GetFirstChild(child)
                while grand.IsOk():
                    if self.skillTreeListCtrl.GetItemText(grand) != "dummy":
                        _, skillID = self.skillTreeListCtrl.GetItemData(grand)
                        _setTreeSkillLevel(grand, skillID)
                    grand = self.skillTreeListCtrl.GetNextSibling(grand)

            child = self.skillTreeListCtrl.GetNextSibling(child)

        dirtySkills = sChar.getDirtySkills(char.ID)
        dirtyGroups = set([skill.item.group.ID for skill in dirtySkills])

        parentID = self.skillTreeListCtrl.GetItemParent(selection)
        parent = self.skillTreeListCtrl.GetItemData(parentID)

        if parent:
            if parent[1] in dirtyGroups:
                self.skillTreeListCtrl.SetItemImage(parentID, self.skillBookImageId)

        event.Skip()


class ImplantEditorView(BaseImplantEditorView):
    def __init__(self, parent, charEditor):
        BaseImplantEditorView.__init__(self, parent)

        self.determineEnabled()
        charEditor.Bind(GE.CHAR_CHANGED, self.contextChanged)

        self.pluggedImplantsTree.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)

    def bindContext(self):
        self.Parent.Parent.entityEditor.Bind(wx.EVT_CHOICE, self.contextChanged)

    def contextChanged(self, event):
        BaseImplantEditorView.contextChanged(self, event)
        self.determineEnabled()

    def getImplantsFromContext(self):
        sChar = Character.getInstance()
        char = self.Parent.Parent.entityEditor.getActiveEntity()

        return sChar.getImplants(char.ID)

    def addImplantToContext(self, item):
        sChar = Character.getInstance()
        char = self.Parent.Parent.entityEditor.getActiveEntity()

        sChar.addImplant(char.ID, item.ID)

    def removeImplantFromContext(self, implant):
        sChar = Character.getInstance()
        char = self.Parent.Parent.entityEditor.getActiveEntity()

        sChar.removeImplant(char.ID, implant)

    def addImplants(self, implants):
        charEditor = self.Parent.Parent
        char = charEditor.entityEditor.getActiveEntity()

        sChar = Character.getInstance()
        for implant in implants:
            sChar.addImplant(char.ID, implant.item.ID)

        wx.PostEvent(charEditor, GE.CharChanged())

    def spawnMenu(self, event):
        context = (("植入体编辑",),)
        menu = ContextMenu.getMenu(self, None, None, *context)

        if menu:
            self.PopupMenu(menu)
        else:
            pyfalog.debug("ContextMenu.getMenu returned false do not attempt PopupMenu")

    def determineEnabled(self):
        char = self.Parent.Parent.entityEditor.getActiveEntity()

        if char.name in ("All 0", "All 5"):
            self.Enable(False)
        else:
            self.Enable()


class APIView(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300),
                          style=wx.TAB_TRAVERSAL)
        self.charEditor = self.Parent.Parent  # first parent is Notebook, second is Character Editor
        self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))

        pmainSizer = wx.BoxSizer(wx.VERTICAL)

        hintSizer = wx.BoxSizer(wx.HORIZONTAL)
        hintSizer.AddStretchSpacer()
        self.stDisabledTip = wx.StaticText(self, wx.ID_ANY,
                                           "你不能链接一个全0级技能或全5级技能到一个EVE角色。\n"
                                           "请选择另一个角色或生成一个新的.", style=wx.ALIGN_CENTER)
        self.stDisabledTip.Wrap(-1)
        hintSizer.Add(self.stDisabledTip, 0, wx.TOP | wx.BOTTOM, 10)

        self.stDisabledTip.Hide()
        hintSizer.AddStretchSpacer()
        pmainSizer.Add(hintSizer, 0, wx.EXPAND, 5)

        fgSizerInput = wx.FlexGridSizer(1, 3, 0, 0)
        fgSizerInput.AddGrowableCol(1)
        fgSizerInput.SetFlexibleDirection(wx.BOTH)
        fgSizerInput.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)

        self.m_staticCharText = wx.StaticText(self, wx.ID_ANY, "角色:", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_staticCharText.Wrap(-1)
        fgSizerInput.Add(self.m_staticCharText, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        self.charChoice = wx.Choice(self, wx.ID_ANY, style=0)
        fgSizerInput.Add(self.charChoice, 1, wx.TOP | wx.BOTTOM | wx.EXPAND, 10)

        self.fetchButton = wx.Button(self, wx.ID_ANY, "获取技能", wx.DefaultPosition, wx.DefaultSize, 0)
        self.fetchButton.Bind(wx.EVT_BUTTON, self.fetchSkills)
        fgSizerInput.Add(self.fetchButton, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        pmainSizer.Add(fgSizerInput, 0, wx.EXPAND, 5)

        pmainSizer.AddStretchSpacer()

        self.m_staticline1 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
        pmainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.ALL, 10)

        self.noCharactersTip = wx.StaticText(self, wx.ID_ANY, "在这个列表中不能看到你的角色?", style=wx.ALIGN_CENTER)

        self.noCharactersTip.Wrap(-1)
        pmainSizer.Add(self.noCharactersTip, 0, wx.CENTER | wx.TOP | wx.BOTTOM, 0)

        self.addButton = wx.Button(self, wx.ID_ANY, "用EVE SSO登录", wx.DefaultPosition, wx.DefaultSize, 0)
        self.addButton.Bind(wx.EVT_BUTTON, self.addCharacter)
        pmainSizer.Add(self.addButton, 0, wx.ALL | wx.ALIGN_CENTER, 10)

        self.charEditor.mainFrame.Bind(GE.EVT_SSO_LOGOUT, self.ssoListChanged)
        self.charEditor.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.ssoListChanged)
        self.charEditor.entityEditor.Bind(wx.EVT_CHOICE, self.charChanged)

        self.charChoice.Bind(wx.EVT_CHOICE, self.ssoCharChanged)

        self.SetSizer(pmainSizer)
        self.Layout()
        self.ssoListChanged(None)

    def ssoCharChanged(self, event):
        sChar = Character.getInstance()
        activeChar = self.charEditor.entityEditor.getActiveEntity()
        ssoChar = self.getActiveCharacter()
        sChar.setSsoCharacter(activeChar.ID, ssoChar)

        self.fetchButton.Enable(ssoChar is not None)

        event.Skip()

    def fetchSkills(self, evt):
        sChar = Character.getInstance()
        char = self.charEditor.entityEditor.getActiveEntity()
        sChar.apiFetch(char.ID, APIView.fetchCallback)

    def addCharacter(self, event):
        sEsi = Esi.getInstance()
        sEsi.login()

    def getActiveCharacter(self):
        selection = self.charChoice.GetCurrentSelection()
        return self.charChoice.GetClientData(selection) if selection != -1 else None

    def ssoListChanged(self, event):
        if not self:  # todo: fix event not unbinding properly
            return

        self.charChanged(event)

    def charChanged(self, event):
        sChar = Character.getInstance()
        sEsi = Esi.getInstance()

        activeChar = self.charEditor.entityEditor.getActiveEntity()

        if event and event.EventType == GE.EVT_SSO_LOGIN.typeId and hasattr(event, 'character'):
            # Automatically assign the character that was just logged into
            sChar.setSsoCharacter(activeChar.ID, event.character.ID)

        sso = sChar.getSsoCharacter(activeChar.ID)

        self.fetchButton.Enable(sso is not None)

        ssoChars = sEsi.getSsoCharacters()

        self.charChoice.Clear()

        noneID = self.charChoice.Append("None", None)

        for char in ssoChars:
            currId = self.charChoice.Append(char.characterName, char.ID)

            if sso is not None and char.ID == sso.ID:
                self.charChoice.SetSelection(currId)

        if sso is None:
            self.charChoice.SetSelection(noneID)

        #
        # if chars:
        #     for charName in chars:
        #         self.charChoice.Append(charName)
        #     self.charChoice.SetStringSelection(char)
        # else:
        #     self.charChoice.Append("No characters...", 0)
        #     self.charChoice.SetSelection(0)
        #
        if activeChar.name in ("All 0", "All 5"):
            self.Enable(False)
            self.stDisabledTip.Show()
            self.Layout()
        else:
            self.Enable()
            self.stDisabledTip.Hide()
            self.Layout()

        if event is not None:
            event.Skip()

    @staticmethod
    def fetchCallback(e=None):
        if e:
            pyfalog.warn("Error fetching skill information for character for __fetchCallback")
            exc_type, exc_value, exc_trace = e
            if config.debug:
                exc_value = ''.join(traceback.format_exception(exc_type, exc_value, exc_trace))
            pyfalog.warn(exc_value)

            wx.MessageBox(
                "获取技能信息错误",
                "错误", wx.ICON_ERROR | wx.STAY_ON_TOP)
        else:
            wx.MessageBox(
                "成功获取技能", "成功", wx.ICON_INFORMATION | wx.STAY_ON_TOP)


class SecStatusDialog(wx.Dialog):

    def __init__(self, parent, sec):
        super().__init__(parent, title="设置安全状态", size=(300, 175), style=wx.DEFAULT_DIALOG_STYLE)

        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)

        bSizer1 = wx.BoxSizer(wx.VERTICAL)

        self.m_staticText1 = wx.StaticText(self, wx.ID_ANY,
                                        "安全状态被用在一些统合部结构计算中",
                                        wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_staticText1.Wrap(-1)
        bSizer1.Add(self.m_staticText1, 1, wx.ALL | wx.EXPAND, 5)

        self.floatSpin = FloatSpin(self, value=sec, min_val=-5.0, max_val=5.0, increment=0.1, digits=2, size=(-1, -1))
        bSizer1.Add(self.floatSpin, 0, wx.ALIGN_CENTER | wx.ALL, 5)

        btnOk = wx.Button(self, wx.ID_OK)
        bSizer1.Add(btnOk, 0, wx.ALIGN_RIGHT | wx.ALL, 5)

        self.SetSizer(bSizer1)
        self.Layout()

        self.Center(wx.BOTH)
